
Simulation = {}

function Simulation.new(sim)
	local o = {
		groupqueue = {},
		num_groupqueue = 0,
		gatequeue = {},
		num_gatequeue = 0,
		groupfxqueue = {},
		current_tick = 0,
		
		definitions = {},
		wires = {},
		gates = {},
		nwires = 0,
		ngates = 0,
		ninports = 0,
		noutports = 0,
		
		initqueue = nil,
		inputqueue = nil,
		tickqueue = {},
		callbacks = nil,
		
	}
	setmetatable(o, sim)
	sim.__index = sim
	return o
end

function Simulation.addtoworld(sim, obj, x, y, z)
	if sim[x] == nil then
		sim[x] = {}
	end
	
	if sim[x][y] == nil then
		sim[x][y] = {}
	end
	
	if sim[x][y][z] == nil then
		sim[x][y][z] = {}
	end
	
	sim[x][y][z][obj] = obj
end

function Simulation.getfromworld(sim, x, y, z)
	if sim[x] == nil or sim[x][y] == nil or sim[x][y][z] == nil then
		return {}
	else
		return sim[x][y][z]
	end
end

function Simulation.getdefinitionbyref(sim, objref)
	return sim.definitions[objref]
end

function Simulation.getgatebyref(sim, objref)
	return sim.gates[objref]
end

function Simulation.getwirebyref(sim, objref)
	return sim.wires[objref]
end

function Simulation.addgatedefinition(sim, definition)
	sim.definitions[definition.objref] = definition
end

function Simulation.addwire(sim, wire)
	sim.wires[Wire.getobjref(wire)] = wire
	
	local bounds = Wire.getbounds(wire)

	for x = bounds[1]+1, bounds[4]-1, 2 do
		for z = bounds[3]+1, bounds[6]-1, 2 do
			Simulation.addtoworld(sim, wire, x, bounds[2], z)
			Simulation.addtoworld(sim, wire, x, bounds[5], z)
		end
	end

	for y = bounds[2]+1, bounds[5]-1, 2 do
		for z = bounds[3]+1, bounds[6]-1, 2 do
			Simulation.addtoworld(sim, wire, bounds[1], y, z)
			Simulation.addtoworld(sim, wire, bounds[4], y, z)
		end
	end

	for x = bounds[1]+1, bounds[4]-1, 2 do
		for y = bounds[2]+1, bounds[5]-1, 2 do
			Simulation.addtoworld(sim, wire, x, y, bounds[3])
			Simulation.addtoworld(sim, wire, x, y, bounds[6])
		end
	end

	sim.nwires = sim.nwires + 1
	Simulation.connectwire(sim, wire)
end

function Simulation.addgate(sim, gate)
	sim.gates[gate.objref] = gate

	for k, port in pairs(gate.ports) do
		local offset = Port.getconnectionposition(port)
		Simulation.addtoworld(sim, port, offset[1], offset[2], offset[3])
		Simulation.connectport(sim, port)

		if Port.gettype(port) == PortTypes.input then
			sim.ninports = sim.ninports + 1
		elseif Port.gettype(port) == PortTypes.output then
			sim.noutports = sim.noutports + 1
		end
	end

	sim.ngates = sim.ngates + 1
	
	Simulation.queuegateinit(sim, gate)
	Simulation.queuegate(sim, gate)
end

function Simulation.removewire(sim, objref)
	local wire = sim.wires[objref]
	if wire ~= nil then
		sim.wires[objref] = nil
		
		local bounds = Wire.getbounds(wire)
		
		for x = bounds[1]+1, bounds[4]-1, 2 do
			for z = bounds[3]+1, bounds[6]-1, 2 do
				sim[x][bounds[2]][z][wire] = nil
				sim[x][bounds[5]][z][wire] = nil
			end
		end
		
		for y = bounds[2]+1, bounds[5]-1, 2 do
			for z = bounds[3]+1, bounds[6]-1, 2 do
				sim[bounds[1]][y][z][wire] = nil
				sim[bounds[4]][y][z][wire] = nil
			end
		end
		
		for x = bounds[1]+1, bounds[4]-1, 2 do
			for y = bounds[2]+1, bounds[5]-1, 2 do
				sim[x][y][bounds[3]][wire] = nil
				sim[x][y][bounds[6]][wire] = nil
			end
		end
		
		sim.nwires = sim.nwires - 1
		Group.removewire(Wire.getgroup(wire), wire)
	end
end

function Simulation.removegate(sim, objref)
	local gate = sim.gates[objref]
	if gate ~= nil then
		for k, port in pairs(gate.ports) do
			local pos = Port.getconnectionposition(port)
			sim[pos[1]][pos[2]][pos[3]][port] = nil
			Group.removeport(Port.getgroup(port), port)
			
			if Port.gettype(port) == PortTypes.input then
				sim.ninports = sim.ninports - 1
			elseif Port.gettype(port) == PortTypes.output then
				sim.noutports = sim.noutports - 1
			end
		end
	end
	
	Simulation.dequeuegate(sim, gate)
	sim.gates[objref] = nil
	sim.ngates = sim.ngates - 1
end

local function is_wire(obj)
	return obj.layer~=nil
end

function Simulation.connectwireat(sim, wire, x, y, z)
	local objs = Simulation.getfromworld(sim, x, y, z)
	for k, obj in pairs(objs) do
		if obj ~= wire and obj.group ~= nil then
			if is_wire(obj) then -- wire
				if Wire.getlayer(obj) == Wire.getlayer(wire) then -- same layer
					Group.addwire(obj.group, wire)
				end
			else -- port
				Group.addwire(obj.group, wire)
			end
		end
	end
end

function Simulation.connectwire(sim, wire)
	local bounds = Wire.getbounds(wire)
	
	for x = bounds[1]+1, bounds[4]-1, 2 do
		for z = bounds[3]+1, bounds[6]-1, 2 do
			Simulation.connectwireat(sim, wire, x, bounds[2], z)
			Simulation.connectwireat(sim, wire, x, bounds[5], z)
		end
	end

	for y = bounds[2]+1, bounds[5]-1, 2 do
		for z = bounds[3]+1, bounds[6]-1, 2 do
			Simulation.connectwireat(sim, wire, bounds[1], y, z)
			Simulation.connectwireat(sim, wire, bounds[4], y, z)
		end
	end

	for x = bounds[1]+1, bounds[4]-1, 2 do
		for y = bounds[2]+1, bounds[5]-1, 2 do
			Simulation.connectwireat(sim, wire, x, y, bounds[3])
			Simulation.connectwireat(sim, wire, x, y, bounds[6])
		end
	end
	
	if Wire.getgroup(wire)==nil then
		Group.addwire(Group.new(), wire)
	end
end

function Simulation.connectport(sim, port)
	local connpos = Port.getconnectionposition(port)
	local objs = Simulation.getfromworld(sim, connpos[1], connpos[2], connpos[3])
	for k, obj in pairs(objs) do
		if obj ~= port and obj.group ~= nil then
			Group.addport(obj.group, port)
		end
	end

	if Port.getgroup(port) == nil then
		Group.addport(Group.new(), port)
	end
end

-- Logic Critical
function Simulation.queuegate(sim, gate)
	if gate.in_queue==0 then
		table.insert(sim.gatequeue, gate)
		sim.num_gatequeue = sim.num_gatequeue + 1
		gate.in_queue = 1
	end
end

function Simulation.queuegatelater(sim, gate, delay)
	local tick = sim.current_tick + delay
	if sim.tickqueue[tick] == nil then
		sim.tickqueue[tick] = {}
	end
	sim.tickqueue[tick][gate] = gate
end

function Simulation.queuegateinput(sim, gate, argv)
	sim.inputqueue = sim.inputqueue or {}
	sim.inputqueue[gate] = argv
end

function Simulation.queuegateinit(sim, gate)
	sim.initqueue = sim.initqueue or {}
	sim.initqueue[gate] = gate
end

-- Logic Critical
function Simulation.queuegroup(sim, group)
	if group.in_queue==0 then
		--table.insert(sim.groupqueue, group)
		sim.groupqueue[sim.num_groupqueue+1] = group
		sim.num_groupqueue = sim.num_groupqueue + 1
		group.in_queue = 1
	end
end

function Simulation.dequeuegroup(sim, group)
	if group.in_queue~=0 then
		array_remove(sim.groupqueue, group)
		sim.num_groupqueue = sim.num_groupqueue - 1
		group.in_queue = 0
	end
	sim.groupfxqueue[group] = nil
end

function Simulation.dequeuegate(sim, gate)
	if gate.in_queue~=0 then
		array_remove(sim.gatequeue, gate)
		sim.num_gatequeue = sim.num_gatequeue - 1
		gate.in_queue = 0
	end
	if sim.inputqueue~=nil then sim.inputqueue[gate] = nil end
	if sim.initqueue ~=nil then sim.initqueue [gate] = nil end
	for tick, tickq in pairs(sim.tickqueue) do
		tickq[gate] = nil
	end
end

function Simulation.queuegroupfx(sim, group)
	sim.groupfxqueue[group] = group
end

function Simulation.queuecallback(sim, gate, ...)
	sim.callbacks = sim.callbacks or {}
	sim.callbacks[gate.objref] = {...}
end

-- Logic Critical
function Simulation.ticklogic(sim)
	--for k, group in ipairs(sim.groupqueue) do
	local len = sim.num_groupqueue
	for i = 1, len do
		local group = sim.groupqueue[i]
		Group.update(group)
		group.in_queue = 0
	end
	sim.groupqueue = {}
	sim.num_groupqueue = 0
	
	if sim.tickqueue[sim.current_tick] ~= nil then
		for i, gate in pairs(sim.tickqueue[sim.current_tick]) do
			Simulation.queuegate(sim, gate)
		end
		sim.tickqueue[sim.current_tick] = nil
	end
	
	--for k, gate in ipairs(sim.gatequeue) do
	local len = sim.num_gatequeue
	for i = 1, len do
		local gate = sim.gatequeue[i]
		gate.logic(gate)
		gate.in_queue = 0
	end
	sim.gatequeue = {}
	sim.num_gatequeue = 0
	
	sim.current_tick = sim.current_tick + 1
end

function Simulation.tickinit(sim)
	if sim.initqueue ~= nil then
		for k, gate in pairs(sim.initqueue) do
			Gate.init(gate)
		end
		sim.initqueue = nil
	end
end

function Simulation.tickinput(sim)
	if sim.inputqueue ~= nil then
		for gate, argv in pairs(sim.inputqueue) do
			Gate.input(gate, argv)
		end
		sim.inputqueue = nil
	end
end

function Simulation.sendfxupdate(sim)
	for k, group in pairs(sim.groupfxqueue) do
		if group.state ~= group.fxstate then
			group.fxstate = group.state
			
			local data = group.state
			
			for i, wire in pairs(group.wires) do
				data = data .. "\t" .. Wire.getobjref(wire)
			end
			
			network_send("WU\t" .. data .. "\n")
		end
	end
	
	sim.groupfxqueue = {}
end

function Simulation.sendcallbacks(sim)
	if sim.callbacks ~= nil then
		local data = "CB"
		
		for objref, args in pairs(sim.callbacks) do
			local escargs = {}
			for argidx, argv in ipairs(args) do
				table.insert(escargs, expandescape(tostring(argv)))
			end
			data = data .. "\t" .. objref .. "\t"..(#escargs)..(#escargs>0 and ("\t"..table.concat(escargs, "\t")) or "")
		end
		
		network_send(data .. "\n")
		sim.callbacks = nil
	end
end
